/*
Copyright (C) 2011 The University of Michigan

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Please send inquiries to powertutor@umich.edu
 */

package edu.umich.PowerTutor.ui;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;
import java.text.DecimalFormat;
import java.util.Arrays;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import edu.umich.PowerTutor.service.ICounterService;
import edu.umich.PowerTutor.service.PowerEstimator;
import edu.umich.PowerTutor.service.UMLoggerService;
import edu.umich.PowerTutor.service.UidInfo;
import edu.umich.PowerTutor.util.Counter;
import edu.umich.PowerTutor.util.Recycler;
import edu.umich.PowerTutor.util.SystemInfo;

public class PowerTop extends Activity implements Runnable {
	private static final String TAG = "PowerTop";
	private static final double HIDE_UID_THRESHOLD = 0.1;

	public static final int KEY_CURRENT_POWER = 0;
	public static final int KEY_AVERAGE_POWER = 1;
	public static final int KEY_TOTAL_ENERGY = 2;
	private static final CharSequence[] KEY_NAMES = { "Current power",
			"Average power", "Energy usage" };

	private SharedPreferences prefs;
	private int noUidMask;
	private String[] componentNames;

	private Intent serviceIntent;
	private CounterServiceConnection conn;
	private ICounterService counterService;
	private Handler handler;

	private LinearLayout topGroup;
	private LinearLayout filterGroup;
	private LinearLayout mainView;

	private static File file;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		prefs = PreferenceManager.getDefaultSharedPreferences(this);
		serviceIntent = new Intent(this, UMLoggerService.class);
		conn = new CounterServiceConnection();
		if (savedInstanceState != null) {
			componentNames = savedInstanceState
					.getStringArray("componentNames");
			noUidMask = savedInstanceState.getInt("noUidMask");
			
		}
		
		Log.e(TAG, "noUidMask: " + noUidMask);

		topGroup = new LinearLayout(this);
		topGroup.setOrientation(LinearLayout.VERTICAL);
		ScrollView scrollView = new ScrollView(this);
		scrollView.addView(topGroup);
		filterGroup = new LinearLayout(this);
		filterGroup.setOrientation(LinearLayout.HORIZONTAL);
		filterGroup.setMinimumHeight(50);
		mainView = new LinearLayout(this);
		mainView.setOrientation(LinearLayout.VERTICAL);
		mainView.addView(filterGroup);
		mainView.addView(scrollView);

		// added by Ting Wu, to identify the view
		/*
		 * AlertDialog.Builder builder = new AlertDialog.Builder(this);
		 * builder.setTitle("PowerTop.java");
		 * builder.setMessage("This view is written by 'PowerTop.java'");
		 * builder.setPositiveButton("OK", new DialogInterface.OnClickListener()
		 * {
		 * 
		 * @Override public void onClick(DialogInterface dialog, int which) { //
		 * TODO Auto-generated method stub Toast.makeText(PowerTop.this, "OK",
		 * Toast.LENGTH_LONG).show(); } });
		 * 
		 * builder.create().show();
		 */

		file = new File(Environment.getExternalStorageDirectory(),
				"ApplicationPowerTrace" + System.currentTimeMillis() + ".log");
	}

	@Override
	protected void onResume() {
		super.onResume();
		handler = new Handler();
		handler.postDelayed(this, 100);
		getApplicationContext().bindService(serviceIntent, conn, 0);

		refreshView();
	}

	@Override
	protected void onPause() {
		super.onPause();
		getApplicationContext().unbindService(conn);
		handler.removeCallbacks(this);
		handler = null;
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putStringArray("componentNames", componentNames);
		outState.putInt("noUidMask", noUidMask);
	}

	private static final int MENU_KEY = 0;
	private static final int MENU_WINDOW = 1;
	private static final int DIALOG_KEY = 0;
	private static final int DIALOG_WINDOW = 1;

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(0, MENU_KEY, 0, "Display Type");
		menu.add(0, MENU_WINDOW, 0, "Time Span");
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		/*
		 * We need to make sure that the user can't cause any of the dialogs to
		 * be created before we have contacted the Power Tutor service to get
		 * the component names and such.
		 */
		for (int i = 0; i < menu.size(); i++) {
			menu.getItem(i).setEnabled(counterService != null);
		}
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case MENU_KEY:
			showDialog(DIALOG_KEY);
			return true;
		case MENU_WINDOW:
			showDialog(DIALOG_WINDOW);
			return true;
		}
		return false;
	}

	@Override
	protected Dialog onCreateDialog(int id) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		switch (id) {
		case DIALOG_KEY:
			builder.setTitle("Select sort key");
			builder.setItems(KEY_NAMES, new DialogInterface.OnClickListener() {
				public void onClick(DialogInterface dialog, int item) {
					prefs.edit().putInt("topKeyId", item).commit();
				}
			});
			return builder.create();
		case DIALOG_WINDOW:
			builder.setTitle("Select window type");
			builder.setItems(Counter.WINDOW_NAMES,
					new DialogInterface.OnClickListener() {
						public void onClick(DialogInterface dialog, int item) {
							prefs.edit().putInt("topWindowType", item).commit();
						}
					});
			return builder.create();
		}
		return null;
	}

	private void refreshView() {
		if (counterService == null) {
			TextView loadingText = new TextView(this);
			loadingText.setText("Waiting for profiler service...");
			loadingText.setGravity(Gravity.CENTER);
			setContentView(loadingText);
			return;
		}

		int keyId = prefs.getInt("topKeyId", KEY_TOTAL_ENERGY);
		try {
			byte[] rawUidInfo = counterService.getUidInfo(
					prefs.getInt("topWindowType", Counter.WINDOW_TOTAL),
					noUidMask | prefs.getInt("topIgnoreMask", 0));
			if (rawUidInfo != null) {
				UidInfo[] uidInfos = (UidInfo[]) new ObjectInputStream(
						new ByteArrayInputStream(rawUidInfo)).readObject();
				double total = 0;
				for (UidInfo uidInfo : uidInfos) {
					if (uidInfo.uid == SystemInfo.AID_ALL)
						continue;
					switch (keyId) {
					case KEY_CURRENT_POWER:
						uidInfo.key = uidInfo.currentPower;
						uidInfo.unit = "W";
						break;
					case KEY_AVERAGE_POWER:
						uidInfo.key = uidInfo.totalEnergy
								/ (uidInfo.runtime == 0 ? 1 : uidInfo.runtime);
						uidInfo.unit = "W";
						break;
					case KEY_TOTAL_ENERGY:
						uidInfo.key = uidInfo.totalEnergy;
						uidInfo.unit = "J";
						break;
					default:
						uidInfo.key = uidInfo.currentPower;
						uidInfo.unit = "W";
					}
					total += uidInfo.key;
				}
				if (total == 0)
					total = 1;
				for (UidInfo uidInfo : uidInfos) {
					uidInfo.percentage = 100.0 * uidInfo.key / total;
				}
				Arrays.sort(uidInfos);

				int sz = 0;
				for (int i = 0; i < uidInfos.length; i++) {
					if (uidInfos[i].uid == SystemInfo.AID_ALL
							|| uidInfos[i].percentage < HIDE_UID_THRESHOLD) {
						continue;
					}
					UidPowerView powerView;
					if (sz < topGroup.getChildCount()) {
						powerView = (UidPowerView) topGroup.getChildAt(sz);
					} else {
						powerView = UidPowerView.obtain(this, getIntent());
						topGroup.addView(powerView);
					}
					powerView.setBackgroundDrawable(null);
					powerView.setBackgroundColor((sz & 1) == 0 ? 0xFF000000
							: 0xFF222222);
					powerView.init(uidInfos[i], keyId);
					sz++;
				}
				for (int i = sz; i < topGroup.getChildCount(); i++) {
					UidPowerView powerView = (UidPowerView) topGroup
							.getChildAt(i);
					powerView.recycle();
				}
				topGroup.removeViews(sz, topGroup.getChildCount() - sz);
			}
		} catch (IOException e) {
		} catch (RemoteException e) {
		} catch (ClassNotFoundException e) {
		} catch (ClassCastException e) {
		}
		setContentView(mainView);
		if (keyId == KEY_CURRENT_POWER) {
			setTitle(KEY_NAMES[keyId]);
		} else {
			setTitle(KEY_NAMES[keyId]
					+ " over "
					+ Counter.WINDOW_DESCS[prefs.getInt("topWindowType",
							Counter.WINDOW_TOTAL)]);
		}
	}

	public void run() {
		refreshView();
		if (handler != null) {
			handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL);
		}
	}

	private static class UidPowerView extends LinearLayout {
		private static Recycler<UidPowerView> recycler = new Recycler<UidPowerView>();
		private static DecimalFormat formatter = new DecimalFormat("0.0");

		public static UidPowerView obtain(Activity activity, Intent startIntent) {
			UidPowerView result = recycler.obtain();
			if (result == null)
				return new UidPowerView(activity, startIntent);
			return result;
		}

		public void recycle() {
			recycler.recycle(this);
		}

		private UidInfo uidInfo;
		private String name;
		private Drawable icon;

		private ImageView imageView;
		private TextView textView;

		private UidPowerView(final Activity activity, final Intent startIntent) {
			super(activity);
			setMinimumHeight(50);
			setOrientation(LinearLayout.HORIZONTAL);
			imageView = new ImageView(activity);
			imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
			imageView.setAdjustViewBounds(true);
			imageView.setMaxHeight(40);
			imageView.setMaxWidth(40);
			imageView.setMinimumWidth(50);
			imageView.setLayoutParams(new ViewGroup.LayoutParams(
					ViewGroup.LayoutParams.WRAP_CONTENT,
					ViewGroup.LayoutParams.FILL_PARENT));
			textView = new TextView(activity);
			textView.setGravity(Gravity.CENTER_VERTICAL);
			textView.setLayoutParams(new ViewGroup.LayoutParams(
					ViewGroup.LayoutParams.FILL_PARENT,
					ViewGroup.LayoutParams.FILL_PARENT));
			addView(imageView);
			addView(textView);
			setOnClickListener(new View.OnClickListener() {
				public void onClick(View v) {
					Intent viewIntent = new Intent(v.getContext(),
							PowerTabs.class);
					viewIntent.putExtras(startIntent);
					viewIntent.putExtra("uid", uidInfo.uid);
					activity.startActivityForResult(viewIntent, 0);
				}
			});
			setFocusable(true);
		}

		public void init(UidInfo uidInfo, int keyType) {
			SystemInfo sysInfo = SystemInfo.getInstance();
			this.uidInfo = uidInfo;
			PackageManager pm = getContext().getPackageManager();
			name = sysInfo.getUidName(uidInfo.uid, pm);
			icon = sysInfo.getUidIcon(uidInfo.uid, pm);
			imageView.setImageDrawable(icon);
			String prefix;
			if (uidInfo.key > 1e12) {
				prefix = "G";
				uidInfo.key /= 1e12;
			} else if (uidInfo.key > 1e9) {
				prefix = "M";
				uidInfo.key /= 1e9;
			} else if (uidInfo.key > 1e6) {
				prefix = "k";
				uidInfo.key /= 1e6;
			} else if (uidInfo.key > 1e3) {
				prefix = "";
				uidInfo.key /= 1e3;
			} else {
				prefix = "m";
			}
			long secs = (long) Math.round(uidInfo.runtime);

			textView.setText(String.format(
					"%1$.1f%% [%3$d:%4$02d:%5$02d] %2$s \n" + "%6$.1f %7$s%8$s",
					uidInfo.percentage, name, secs / 60 / 60, (secs / 60) % 60,
					secs % 60, uidInfo.key, prefix, uidInfo.unit));
			/*
			String data = System.currentTimeMillis()
					+ "["
					+ name
					+ "]"
					+ String.format("%1$.1f%% [%3$d:%4$02d:%5$02d] %2$s "
							+ "%6$.1f %7$s%8$s", uidInfo.percentage,
							uidInfo.uid, secs / 60 / 60, (secs / 60) % 60,
							secs % 60, uidInfo.key, prefix, uidInfo.unit) + "\n";
			
			try {
				RandomAccessFile raf = new RandomAccessFile(file, "rw");
				raf.seek(file.length());
				raf.write(data.getBytes());
				raf.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			
			*/
			
			// System.out.println(String.format("%1$.1f%% [%3$d:%4$02d:%5$02d] %2$s\n"
			// +
			// "%6$.1f %7$s%8$s",
			// uidInfo.percentage, name, secs / 60 / 60, (secs / 60) % 60,
			// secs % 60, uidInfo.key, prefix, uidInfo.unit));
		}
	}

	private class CounterServiceConnection implements ServiceConnection {
		public void onServiceConnected(ComponentName className,
				IBinder boundService) {
			counterService = ICounterService.Stub
					.asInterface((IBinder) boundService);
			try {
				componentNames = counterService.getComponents();
				noUidMask = counterService.getNoUidMask();
				filterGroup.removeAllViews();
				for (int i = 0; i < componentNames.length; i++) {
					int ignMask = prefs.getInt("topIgnoreMask", 0);
					if ((noUidMask & 1 << i) != 0)
						continue;
					final TextView filterToggle = new TextView(PowerTop.this);
					final int index = i;
					filterToggle.setText(componentNames[i]);
					filterToggle.setGravity(Gravity.CENTER);
					filterToggle
							.setTextColor((ignMask & 1 << index) == 0 ? 0xFFFFFFFF
									: 0xFF888888);
					filterToggle
							.setBackgroundColor(filterGroup.getChildCount() % 2 == 0 ? 0xFF444444
									: 0xFF555555);
					filterToggle.setFocusable(true);
					filterToggle.setOnClickListener(new View.OnClickListener() {
						public void onClick(View v) {
							int ignMask = prefs.getInt("topIgnoreMask", 0);
							if ((ignMask & 1 << index) == 0) {
								prefs.edit()
										.putInt("topIgnoreMask",
												ignMask | 1 << index).commit();
								filterToggle.setTextColor(0xFF888888);
							} else {
								prefs.edit()
										.putInt("topIgnoreMask",
												ignMask & ~(1 << index))
										.commit();
								filterToggle.setTextColor(0xFFFFFFFF);
							}
						}
					});
					filterGroup.addView(filterToggle,
							new LinearLayout.LayoutParams(
									ViewGroup.LayoutParams.FILL_PARENT,
									ViewGroup.LayoutParams.FILL_PARENT, 1f));
				}
			} catch (RemoteException e) {
				counterService = null;
			}
		}

		public void onServiceDisconnected(ComponentName className) {
			counterService = null;
			getApplicationContext().unbindService(conn);
			getApplicationContext().bindService(serviceIntent, conn, 0);
			Log.w(TAG, "Unexpectedly lost connection to service");
		}
	}
}
